باسمه تعالی
کلاسها و متدها
با اینکه ما از امکانات شیگرایی پایتون استفاده کرده ایم، اما برنامههایی که در دو فصل اخیر نوشته ایم در واقع شیگرا نیستند زیرا آنها رابطه میان انواع تعریف شده توسط برنامه نویس و توابعی که روی آنها عملیات انجام میدهند را به نمایش نمیگذارند. قدم بعدی تبدیل این توابع به توابعی است که این رابطه را واضح نمایش دهد.
مثالهای این فصل در آدرس http://thinkpython2.com/code/Time2.py و جوابهای تمرینات در آدرس http://thinkpython2.com/code/Point2_soln.py در دسترس هستند.
#### ۱۷.۱ ویژگیهای شی گرایی
پایتون یک زبان شیگرا است. این بدین معناست که ویگژگیهای یک زبان شیگرا را داراست. این ویژگیها عبارتند از
- برنامهها حاوی تعریف کلاسها و متدها هستند.
-
- اکثر کارهای محاسباتی در قالب انجام عملیات روی اشیا بیان میشوند
-
- اشیا موارد دنیای واقعی را به نمایش گذاشته و متدها معادل نحوه تعامل موارد در دنیای واقعی هستند.
به عنوان مثلا کلاس Time تعریف شده در فصل ۱۶ معادل نحوهای است که افراد در دنیای واقعی زمان را ثبت میکنند. توابع تعریف شده نیز معادل عملیاتی است که افراد با زمان انجام میدهند. همچنین، کلاسهای Point و Rectangle در فصل ۱۵ معادل مفاهیم ریاضی نقطه و مستطیل هستند.
تاکنون ما از هیچ یک از امکاناتی که پایتون برای پشیبانی از شیگرایی ارائه میدهد استفاده نکردهایم. استفاده از این ویژگیها الزامی نیستند. بسیاری از آنها یک syntax جایگزین برای چیزهایی است که هماکنون انجام دادهایم. اما در بسیاری از مواقع، این جایگزین موجز تر بوده و دقیق تر ساختار برنامه را بیان میدارد.
به عنوان مثال در Time1.py هیچ ارتباط واضحی میان تعریف کلاس و تعریف توابع زیر وجود ندارند. با انجام چند آزمایش، مشخص میشود که هریک از توابع حداقل یک آرگومان از یک شیء از نوع Time دارد.
این آزمایش انگیزه اولیه استفاده از متدها است. متد تابعی است که به کلاس خاصی تعلق دارد. ما متدهایی برای رشتهها، لیستها، دیکشنریها و tupleها دیدهایم. در این فصل ما متدها را برای انواع تعریف شده توسط برنامه نویس تعریف میکنیم.
متدها از نظر نحوی همانند توابع هستند، اما دو تفاوت سینتکسی وجود دارد:
- متدها داخل کلاس تعریف میشوند. هدف از تعریف داخل کلاس نمایش صریح کلاس و متدهاست.
-
- سینتکس فرواخوانی متدها نیز از توابع متفاوت است.
در چند بخش آتی، ما توابع فصلهای گذشته را تبدیل متد خواهیم کرد. این تبدیل کاملا ساده است و شما نیز میتوانید هر تابعی را با انجام قدمهایی تبدیل کنید. اگر شما با تبدیل تابع به متد و بالعکس راحت باشید میتوانید انتخاب کنید که کدام شکل برای کار شما بهتر است.
#### ۱۷.۲ پرینت کردن اشیاء
در فصل ۱۶ ما کلاسی به اسم Time تعریف کردیم و در بخش ۱۶.۱ تابعی به اسم print_time توسعه دادیم:
برای فراخوانی این تابع باید شما یک شی از نوع Time را به عنوان آرگومان به تابع بدهید
تنها کار برای تبدیل print_time به یک متد، این است که تعریف تابع را درون تعریف کلاس ببریم. به تغییر در فرورفتگی کد دقت کنید:
حالا دو راه برای فراخوانی متد print_time وجود دارد. اولین راه(غیر معمول) عبارت است از:
در این نحوه استفاده از نقطه، Time اسم کلاس است و print_time نام متد است. start به عنوان یک پارامتر داده میشود.
راه دوم(که کوتاه تر هم هست) این است که سینتکس متد استفاده کنیم:
در این نحوه استفاده از نقطه، print_time (دوباره) اسم متد است و start شی ای است که متد از آن فرواخوانی شده است. نام دیگر این شی موضوع نیز هست. همانند موضوع یک جمله که نشاندهنده محتوای جمله است، موضوع یک متد نیز محتوایی است که متد از روی آن فروخوانی میشود.
درون متد موضوع به اولین پارامتر تخصیص داده میشود، پس در این حالت start به time تخصیص داده میشود.
بصورت قراردادی اولین پارامتر متد self نامیده میشود.پس به شکل معمول تر متد print_time بصورت زیر است
دلیل این قرارداد یک استعاره غیر واضح است:
- سینتکس فرواخوانی تابع، print_time(start) پیشنهاد میدهد که تابع یک عنصر عامل است. مانند این است که بگویمم «ای print_time، این شی مورد نیاز تو برای پرینت کردن است.»
-
- در برنامه نویسی شی گرا، اشیا عناصر فعال هستند. در یک فرواخوانی متد نوع مانند start.print_time() میگوید که «ای start، لطفا خودت را پرینت کن»
این تغییر نحوه نگاه ممکن است مودبانه تر باشد اما بصورت واضح نشان نمیدهد که کاربردی تر است. در مثالهایی که تا کنون دیدم، ممکن است اینگونه نباشد. اما در مواردی تغییر در مسئولیت از توابع به اشیا این امکان را فراهم میآورد که توابع (یا متدهای) تطبیق پذیر تری داشته باشیم. همچنین استفاده مجدد از کد و نگهداری آن آسانتر میشود.
به عنوان تمرین تابع time_to_int (بخش ۱۶.۴) را تبدیل به یک متد کنید. ممکن است که مشتاق شوید که تابع int_to_time را نیز تبدیل به متد کنید اما این عمل منتطق نیست چون شی برای اجرای این متد رویش وجود ندارد
#### ۱۷.۳.یک مثال دیگر
این یک نسخه دیگر تابع version(بخش ۱۶.۳) که به عنوان یک متد نوشته شده است:
در این نسخه فرض بر این است که time_to_int به عنوان یک متد نوشته شده است. همچنین توجه داشته باشید که این یک متد کامل است نه یک تغییر دهنده.
نحوه فراخوانی این متد به شکل زیر است:
شی موضوع به عنوان پارامتر اول که همان self است تخصیص داده میشود. آرگومان 1337 به پارامتر دوم تخصیص داده میشود.
این مکانیزم گیج کننده است، هنگامی که شما خطایی انجام میدهید. به عنوان مثال اگه شما increment را با دو آرگومان فراخوانی کنید شما خواهید داشت:
این پیغام خطا در ابتدا گیج کننده است، بخاطر اینکه تنها دو آرگومان درون پرانتر وجود دارد. اما موضوع نیز به عنوان یک آرگومان در نظر گرفته میشود، پس در مجموع سه آرگومان وجود دارد.
ضمنا، یک آرگومان موضعی آرگومانی است که نام پارامتر را با خود ندارد. این بدین معناست که یک آرگومان با کلمه کلیدی نیست. در این فرواخونی تابع:
آرگومانهای parrot و cage آرگومانهای موضعی و deae آرگومان کلمه کلیدی است.
#### ۱۷.۴ یک مثال پیچیده تر
بازنویسی تابع is_after(بخش ۱۶.۱) اندکی پیچده تر است، بخاطر اینکه این تابع دو شی از نوع Time به عنوان پارامتر دریافت میکند. در این حالت قرارداد این است که نام اولین پارامتر seft و دومین پارامتر other است:
برای استفاده از این متد، شما بایستی آنرا روی یک شی فراخوانی کرده و شی دوم را به عنوان پارامتر استفاده کنید
یکی از حسنهای این سنتکس این است که تقریبا مثل انگلیسی خوانده میشود «end is after start»
#### ۱۷.۵ متد init
متد init (که مخفف initialization)است، یک متد ویژه است که وقتی یک شی ساخته میشود فراخوانی میشود. نام کامل این متد __init__ (دو underscore بعد init و بعد دو underscore دیگر)است. متد init برای کلاس Timeممکن است چیزی شبیه به این باشد.
بطور معمول پارامترهای متد __init__ نامی شبیه ویژگیها کلاس دارند. عبارت زیر :
پارامتر hour را به عنوان مقدار یکی از ویژگیها self ذخیره میکند.
پارامترها اختیاری هستند یعنی اگر شما Time را بدون آرگومان فراخوانی کنید مقادیر اولیه را دریافت میکنید.
اگر یک پارامتر بدهید، ساعت را مقدار خواهید داد:
اگر دو پارامتر را مقدار دهید، ساعت و دقیقه را مقدار داده اید:
اگر سه پارامتر بدهید هر سه پارامتر اولیه نادیده گرفته خواهد شد.
به عنوان تمرین، متد init کلاس Point که x و y را به عنوان پارامتر اختیاری گرفته و به ویژگیهای مرتبطشان تخصیص میدهد را بنویسید
۱۷.۶ متد __str__
متد __str__ یک متد خاص مانند __init__ است که وظیفهاش برگرداندن نسخه رشته ای از یک شی است.
به طور مثال این متد str برای شی Time است:
# inside class Time:\ \ def __str__(self):\ return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
وقتی شما یک شی را پرینت میکنید، پایتون متد str را فراخوانی میکند:
>>> time = Time(9, 45)\ >>> print time\ 09:45:00\
هرگاه من کلاس تازهای در پایتون مینویسم، ابتدا متد init را میسازم که کار نمونه سازی شی را آسان تر میکند و بعد از str را مینویسم که کار عیبیابی (Debugging) را ساده تر میکند.
تمرین ۳
برای کلاس Point یک متد str بنویسید. از این کلاس یک شی درست کنید و آن را پرینت کنید.
۱۷.۷ Operator Overloading
با تعریف متد های مخصوص دیگر، میتوانید رفتار دیگر عملگرها روی انواع (Type) تعریف شده توسط کاربر مشخص کنید. به طور مثال اگر متد __add__ را روی کلاس Time تعریف کنید میتوانید از عملگر + روی اشیای Time استفاده کنید.
مثال زیر شکلی از پیاده سازی نمونه ذکر شده است:
# inside class Time:\ \ def __add__(self, other):\ seconds = self.time_to_int() + other.time_to_int()\ return int_to_time(seconds)
و به این شکل قابل استفاده است:
>>> start = Time(9, 45)\ >>> duration = Time(1, 35)\ >>> print start + duration\ 11:20:00
وقتی در پایتون از عملگر + استفاده میکنید پایتون __add__ را فراخوانی میکند. وقتی نتیجه را پرینت میکنید، پایتون __str__ را فراخوانی میکند. در نتیجه اتفاقات زیادی در پشت صحنه در حال رخ دادن است!
تغییر رفتار عملگر به شکلی که روی انواع تعریف شده توسط کاربر کار کند Operator Overloading نامیده میشود. برای هر عملگر در پایتون یک متد مخصوص برای پاسخگویی وجود دارد. برای اطلاعات بیشتر به این صفحه مراجعه کنید: http://docs.python.org/2/reference/datamodel.html#specialnames
تمرین ۴
یک متد add برای کلاس Point بنویسید.
۱۷.۸ Type-based dispatch
در بخش قبل ما دو شی Time ایجاد کردیم، ولی شاید بخواهید یک عدد صحیح به شی Time اضافه کنید. کد زیر نسخهای از __add__ است که نوع other را بررسی میکند و یکی از add_time یا increment را خطاب میکند:
# inside class Time:\ \ def __add__(self, other):\ if isinstance(other, Time):\ return self.add_time(other)\ else:\ return self.increment(other)\ \ def add_time(self, other):\ seconds = self.time_to_int() + other.time_to_int()\ return int_to_time(seconds)\ \ def increment(self, seconds):\ seconds += self.time_to_int()\ return int_to_time(seconds)
تابع توکار (built-in) isinstance یک مقدار و شی کلاس را میگیرد و اگر مقدار وارد شده نمونهای از کلاس باشد مقدار True را برمیگرداند.
اگر other یک شی Time باشد، __add__ تابع add_time را خطاب میکند. در غیر اینصورت فرض بر این خواهد بود که پارامتر عدد است و تابع increment را فراخوانی میکند. به این کار type-based dispatch میگوید چون محاسبه را به متدهای مختلف بر اساس نوع آرگومانها مخابره میکند.
این مثالی از استفاده عملگر + در انواع مختلف است:
>>> start = Time(9, 45)\ >>> duration = Time(1, 35)\ >>> print start + duration\ 11:20:00\ >>> print start + 1337\ 10:07:17
متاسفانه این شکل پیادهسازی جمع قابلیت جابجایی ندارد. اگر عدد صحیح اولین مقدار باشد، این اتفاق میفتد:
>>> print 1337 + start\ TypeError: unsupported operand type(s) for +: 'int' and 'instance'
مشکل اینجا این است که در اینجا پایتون به جای اینکه از Time بخواهد که یک عدد صحیح به آن اضافه کند، از یک عدد صحیح میخواهد که یک شی Time را با آن جمع کند، و عدد صحیح نمیداند که چطور اینکار را انجام دهد. ولی یک راهحل جالب برای رفع این مشکل وجود دارد: استفاده متد __radd__ که مخفف right-side add (جمع سمت راست) است. این متد هنگامی فراخوانی میشود که شی Time در سمت راست یک جمع ظاهر میشود. این شکلی از پیاده سازی است:
# inside class Time:\ \ def __radd__(self, other):\ return self.__add__(other)\
و اینگونه استفاده میشود:
>>> print 1337 + start\ 10:07:17
تمرین ۵
یک متد add برای کلاس Point بنویسید که یا یک شی Point بگیرد یا یک چندتایی (Tuple):
- اگر مقدار دوم Point بود، متد باید یک شی جدید از Point بسازد که مقدار x مختصات جمع مقادیر x عملوند ها باشد، به همین شکل برای مختصات y.
- اگر عملوند دوم یک چندتایی باشد، متد باید المان اول چندتایی را به x و المان دوم را به y اضافه میکند، و یک Point جدید به عنوان نتیجه برگرداند.
۱۷.۹ چند ریختی (Polymorphism)
Type-based dispatch وقتی به درد میخورد که واقعا مورد نیاز باشد، ولی (خوشبختانه) همیشه مورد نیاز نیست. معمولا میتوانید با نوشتن توابع مختلف با آرگومان ها با انواع مختلف به خوبی کار کنید.
بیشتر توابعی که ما برای رشته ها نوشتیم برای هر شکلی از دنباله کار میکند. به عنوان مثال، در بخش ۱۱.۱ ما از histogram برای شمردن دفعات تکرار یک کاراکتر در یک کلمه استفاده کردیم.
def histogram(s):\ d = dict()\ for c in s:\ if c not in d:\ d[c] = 1\ else:\ d[c] = d[c]+1\ return d\
این تابع برای لیست ها، چندتایی ها و حتی دیکشنری ها کار میکند، تا وقتی که عناصر s قابل هش شدن باشد تا بتوان به عنوان کلید در d از آن استفاده کرد.
>>> t = ['spam', 'egg', 'spam', 'spam', 'bacon', 'spam']\ >>> histogram(t)\ {'bacon': 1, 'egg': 1, 'spam': 4}
توابعی که میتوانند با چند نوع مختلف کار کنند چندریختی نامیده میشوند. چندریختی استفاده دوباره از کد را تسهیل میکند. به طور مثال تابع توکار sum که عناصر یک دنباله را با هم جمع میکند، تا وقتی که عناصر دنباله قابلیت جمع داشته باشند ادامه پیدا میکند.
از آنجا که به اشیای Time یک متد add اضافه کرده ایم، با sum کار میکنند:
>>> t1 = Time(7, 43)\ >>> t2 = Time(7, 41)\ >>> t3 = Time(7, 37)\ >>> total = sum([t1, t2, t3])\ >>> print total\ 23:01:00
به طور کلی، اگر همه عملیات داخل تابع با نوع داده شده کار کند، کل تابع با آن نوع کار میکند.
۱۷.۱۰ رفع مشکل
امکان اضافه کردن ویژگی به اشیا در هر نقطه از اجرای برنامه ممکن است، ولی اگر به تئوری انواع در برنامه احترام میگذارید، کار اشتباهیست که چند شی از یک نوع با ویژگی های متفاوت داشته باشید. معمولا کار بهتر این است که تمام ویژگیهای شی در متد init آن مشخص شده باشد.
اگر مطمئن نیستید که یک شی یک ویژگی خاص دارد یا نه، میتوانید از تابع توکار hasattr استفاده کندی (بخش ۱۵.۷)
راه دیگر دسترسی به یک ویژگی استفاده از ویژگی خاص __dict__ است که یک دیکشنری است که نام و مقدار همه ویژگی ها را برمیگرداند:
>>> p = Point(3, 4)\ >>> print p.__dict__\ {'y': 4, 'x': 3}\
برای رفع مشکلات (Debugging) بهتر است این تابع را به این شکل در دسترس نگه دارید:
def print_attributes(obj):\ for attr in obj.__dict__:\ print attr, getattr(obj, attr)
تابع print_attributes در دیکشنری آیتم های موجود شی گردش میکند و نام و مقدار هر ویژگی را چاپ میکند.
تابع توکار getattr یک شی و نام یک ویژگی را میگیرد و مقدار ویژگی را برمیگرداند.
۱۷.۱۱ واسط و پیاده سازی آن
یکی از اهداف استفاده از شی گرایی این است که نرم افزار ساختار پذیری بیشتری داشته باشد، به این معنی که برنامه در حال کار باشد درحالی دیگر اعضای سیستم در حال تغییرند و برنامه را طوری تغییر دهید که با نیازهای جدید هماهنگ شوند.
یک الگوی طراحی که برای رسیدن به این هدف کمک میکند این است که واسطها را از پیادهسازیها جدا کنید. برای اشیا، اینکار به این معنی است که متدهای کلاس نباید به ویژگیهای آن وابسته باشد.
به طور مثال در این بخش ما کلاسی ایجاد میکنیم که زمان را نشان میدهد. متدهای موجود این کلاس شامل time_to_int، is_after و add_time است.
این متدها را میتوانیم به اشکال مختلف توسعه دهیم. جزییات پیاده سازی بستگی به این دارد که ما چطور زمان را نشان میدهیم. در اینجا، ویژگیهای شی Time شامل hour, minute و second است.
به عنوان راه حل جانبی ما میتوانیم به جای استفاده از این ویژگی ها از یک عدد صحیح که تعداد ثانیهها از نیمه شب را نمایش میدهد استفاده کنیم. اینطور پیاده سازی نوشتن متدهایی مثل is_after را راحت تر میکند ولی در مقابل نوشتن برخی متدهای دیگر سخت میشود.
بعد از پیادهسازی این کلاس ممکن است یک راه پیادهسازی بهتری پیدا کنید. اگر بقیه اعضای برنامه از کلاس شما استفاده میکنند پیاده سازی جدید کار پر خطا و زمانبری خواهد بود.
در حالی که اگر واسط را با دقت پیاده کرده باشید میتوانید پیادهسازی را عوض کنید بدون اینکه واسط را دستکاری کنید، به این معنی که بقیه اعضای برنامه تغییر نخواهد کرد.
جدا کردن واسط از پیادهسازی به این است که باید ویژگیها را مخفی کنید. کد بقیه اعضای برنامه (خارج از تعریف کلاس) باید از متدهایی برای خواندن و تغییر حالت شی استفاده کنند و نباید به ویژگیها به طور مستقیم دسترسی داشته باشند. به این اصول مخفیسازی اطلاعات (Information hiding) میگویند. م.ش: http://en.wikipedia.org/wiki/Information_hiding
تمرین ۶
کد این بخش را دانلود کنید (http://thinkpython.com/code/Time2.py) و ویژگیهای Time را به یک عدد صحیح نمایش دهنده ثانیهها از نیمه شب تغییر دهید. سپس متدها را تغییر دهید (و تابع int_to_time) تا با پیادهسازی جدید کار کند. کد تست main را اصلا تغییر ندهید. وقتی همه چیز تمام شد خروجی باید دقیقا مثل قبل باشد. راه حل: http://thinkpython.com/code/Time2_soln.py
۱۷.۱۲ دایره لغات
زبان شی گرا (object-oriented language):
زبانی که امکاناتی مانند کلاس های تعریف شده توسط کاربر و syntax متد را در اختیار میگذارد که برنامه نویسی شی گرا را آسان میکند.
برنامه نویسی شی گرا (object-oriented programming):
یک شیوه برنامه نویسی که در آن داده و عملیاتی که آن را تغییر میدهند در کلاسها و متدها مرتب شده است.
متد (method):
تابعی که در تعریف یک کلاس نوشته شده است و در نمونههای آن کلاس قابل فراخوانی است.
subject:
Object ای که متد روی آن اجرا شده است.
operator overloading:
تغییر رفتار یک عملگر مثل + تا روی یک نوع تعریف شده توسط کاربر کار کند.
type-based dispatch:
یک الگوی برنامه نویسی که نوع عملوند را بررسی میکند و بر اساس آن توابع مختلف را صدا میزند.
چند ریختی (polymorphic):
اشاره به نوعی تابع که با انواع مختلف کار میکند.
مخفی سای اطلاعات (information hiding):
اصلی که در آن واسط ارائه شده توسط یک Object نباید به پیاده ساری وابسته باشد، به طور دقیق تر، ویژگی های Object.
۱۷.۱۳ تمرینات
تمرین ۷
این تمرین برای پیدا کردن یکی از رایجترین، در حالی که به سختی قابل پیدا کردن است، خطاهای پایتون است. یک تعریف از کلاسی به اسم Kangaroo بنویسید که متدهای زیر را دارد.
۱. متد __init__ که در آن ویژگی pouch_contents با یک لیست خالی مقداردهی کند.
۲. متدی با نام put_in_pouch که یک Object با هر نوعی میگیرد و به pouch_contents اضافه میکند.
۳. متد __str__ که یک خروجی رشتهای از شی Kangaroo و pouch_contents برمیگرداند.
کد خود را با ایجاد دو شی Kangaroo و دادن دو مقدار kanga و roo به آن و اضافه کردن roo به kanga بررسی کنید.
کد http://thinkpython.com/code/BadKangaroo.py را دانلود کنید. این راه حل شامل یک باگ بزرگ و اذیت کننده هم هست! باگ را پیدا و رفع کنید.
اگر راه حلی پیدا نکردید http://thinkpython.com/code/GoodKangaroo.py را دانلود کنید که مشکل را توضیح میدهد و برای رفع آن راه حلی پیشنهاد میدهد.
تمرین ۸
Visual یک ماژول پایتون است که گرافیک سه بعدی را در اختیار قرار میدهد. این ماژول همیشه در نصب پایتون وجود ندارد در نتیجه ممکن است نیاز باشد که آن را دانلود و نصب کنید. (http://vpython.org/)
مثال زیر یک فضای سه بعدی به طول و عرض و ارتفاع ۲۵۶ واحد ایجاد میکند و مرکز آن را روی نقطه (۱۲۸،۱۲۸،۱۲۸) قرار میدهد. سپس یک کره آبی رسم میکند.
from visual import *\ \ scene.range = (256, 256, 256)\ scene.center = (128, 128, 128)\ \ color = (0.1, 0.1, 0.9) # mostly blue\ sphere(pos=scene.center, radius=128, color=color)
color یک چندتایی RGB است؛ به این معنی که عناصر قرمز، سبز، آبی هستند و مقادیر بین ۰.۰ و ۱.۰ هستند. (http://en.wikipedia.org/wiki/RGB_color_model)
اگر این برنامه را اجرا کنید، باید یک صفحه با پس زمینه سیاه و یک کره آبی رنگ مشاهده کنید. اگر کلید وسط را بالا و پایین کنید میتوانید بزرگ و کوچک نمایی کنید. همچنین میتوانید صفحه را با نگه داشتن کلید راست و کشیدن صفحه بچرخانید، ولی با وجود تنها یک کره در محیط نمیتوان چرخش را تشخیص داد.
حلقه زیر یک مکعب پر از کره ایجاد میکند:
t = range(0, 256, 51)\ for x in t:\ for y in t:\ for z in t:\ pos = x, y, z\ sphere(pos=pos, radius=10, color=color)
۱. این کد را در یک اسکریپت قرار دهید و از اجرای آن اطمینان حاصل کنید.
۲. برنامه را طوری تغییر دهید که هر کره بسته به جایگاه آن رنگش مشخص شود. دقت کنید که فضا از ۰ تا ۲۵۵ است و چندتایی RGB از ۰.۰ تا ۱.۰.
۳. http://thinkpython.com/code/color_list.py را دانلود کنید و تابع read_colors تا یک لیست رنگ های موجود در سیستم را ایجاد و نام و مقادیر RGB آن را بگیرید. به ازای هر نام رنگ یک کره رسم کنید که جایگاه آن وابسته به مقدار RGB آن باشد.